package se.webbzon.boltzmann.game.object.portal;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import se.webbzon.boltzmann.game.environment.GameEnvironment;
import se.webbzon.boltzmann.game.event.AbstractGameEvent;
import se.webbzon.boltzmann.game.object.dialogue.ChoiceDialogue;
import se.webbzon.boltzmann.game.object.dialogue.DialogueBackground;
import se.webbzon.boltzmann.game.object.dialogue.SimpleDialogue;
import se.webbzon.boltzmann.game.object.dialogue.layout.ChoiceDialogueLayout;
import se.webbzon.boltzmann.game.object.dialogue.layout.DefaultChoiceDialogueLayout;
import se.webbzon.boltzmann.game.object.dialogue.layout.DefaultSimpleDialogueLayout;
import se.webbzon.boltzmann.game.object.dialogue.layout.SimpleDialogueLayout;
import se.webbzon.boltzmann.game.sequence.ChoiceDialogueSequenceNode;
import se.webbzon.boltzmann.game.sequence.Sequence;
import se.webbzon.boltzmann.game.sequence.SequenceListener;
import se.webbzon.boltzmann.game.sequence.SequenceNode;
import se.webbzon.boltzmann.game.sequence.SimpleDialogueSequenceNode;
import se.webbzon.boltzmann.input.Input;
import se.webbzon.boltzmann.player.Player;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.WorldStaticObject;

public class SaveEvent extends AbstractGameEvent {
	
	// The environment and input associated with this save event
	private final GameEnvironment genv;
	private final Input input;
	
	// The parent associated with the dialogues of this save event
	private final WorldStaticObject parent;
	
	// The save location of this save event
	private final Location location;
	
	// The dialogues of this event
	private final ChoiceDialogue dSave, dOverwrite, dError;
	private final SimpleDialogue dOk;
	
	// The sequence of this save event
	private Sequence seq;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public SaveEvent(GameEnvironment genv, Input input, WorldStaticObject parent, Location location) {
		this.genv = genv;
		this.input = input;
		this.parent = parent;
		this.location = location;
		
		SimpleDialogueLayout sdl = new DefaultSimpleDialogueLayout();
		ChoiceDialogueLayout cdl = new DefaultChoiceDialogueLayout();
		List<String> wYesNo = Arrays.asList(new String[] {"Yes", "No"});
		List<String> wTryAgainOrLocal = Arrays.asList(new String[] {"Try again", "Save backup", "Abort"});
		
		dSave = new ChoiceDialogue(cdl, DialogueBackground.INFO, "Do you want to save the game?", wYesNo);
		dOverwrite = new ChoiceDialogue(cdl, DialogueBackground.INFO, "This save slot has been used by#another player. Do you want to#overwrite his/her game?", wYesNo);
		dError = new ChoiceDialogue(cdl, DialogueBackground.INFO, "The game could not be saved due#to the following reason:#<D>@message</>", wTryAgainOrLocal);
		dOk = new SimpleDialogue(sdl, DialogueBackground.INFO, "The game was successfully saved!");
	}

	/*============================================================
	Protected Methods
	============================================================*/
	
	@Override protected void onBegin(WorldArea world) {
		seq = new Sequence(genv,world);
		seq.addListener(new SequenceListenerImpl());
		
		ChoiceDialogueSequenceNode nCSave = new ChoiceDialogueSequenceNode(seq,dSave);
		ChoiceDialogueSequenceNode nCOverwrite = new ChoiceDialogueSequenceNode(seq,dOverwrite);
		ChoiceDialogueSequenceNode nCError = new ChoiceDialogueSequenceNode(seq,dError);
		SimpleDialogueSequenceNode nSOk = new SimpleDialogueSequenceNode(seq,dOk);
		SaveNode nSave = new SaveNode(seq,nCError,nSOk,nCOverwrite);
		SaveNodeIncrementer nInc = new SaveNodeIncrementer(seq,nSave);
		seq.addBinding(nCSave, parent);
		seq.addBinding(nCOverwrite, parent);
		seq.addBinding(nCError, parent);
		seq.addBinding(nSOk, parent);
		world.createInstance(dSave);
		world.createInstance(dOverwrite);
		world.createInstance(dError);
		world.createInstance(dOk);
		
		nCSave.setBranches(new SequenceNode[] {nSave, null});
		nCError.setBranches(new SequenceNode[] {nSave, nInc, null});
		seq.begin(input, nCSave, null);
	}

	@Override protected void onEnd() {
		seq.end();
		seq = null;
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of a sequence listener. **/
	private final class SequenceListenerImpl implements SequenceListener {

		@Override public void onSequenceBegin(Sequence sequence) {}

		@Override public void onSequenceEnd(Sequence sequence) {
			end();
		}
		
	}
	
	/** The sequence node used for saving the game. **/
	private final class SaveNode extends SequenceNode {
		
		// The branches of this save node
		private final SequenceNode error, ok, revision; 
		
		// The backup used for this save node
		private int backup;

		public SaveNode(Sequence sequence,
						SequenceNode error, 
						SequenceNode ok,
						SequenceNode revision) {
			super(sequence);
			this.error = error;
			this.ok = ok;
			this.revision = revision;
			backup = 0;
		}
		
		public int getBackup() {
			return backup;
		}
		
		public void setBackup(int backup) {
			this.backup = backup;
		}

		@Override public int branches() {
			return 3;
		}

		@Override public SequenceNode getBranch(int n) {
			switch (n) {
			case 0: return error;
			case 1: return ok;
			default: return revision;
			}
		}

		@Override protected void onBegin(WorldObject binding) {
			Sequence seq = getSequence();
			Collection<Player> players = genv.getPlayers();
			Map<Player,Location> locations = new HashMap<Player,Location>();
			for (Player player : players) {
				locations.put(player, player.getLocation().clone());
				player.getLocation().setLocation(location);
			}
			try {
				genv.getBackup(backup).save(genv);
				seq.next(1);
			} catch (IOException e) {
				seq.getVariables().assign("message", e.getMessage());
				seq.next(0);
			} catch (Exception e) {
				seq.getVariables().assign("message", e.getMessage());
				seq.next(0);
			} finally {
				for (Player player : players) {
					player.getLocation().setLocation(locations.get(player));
				}
			}
		}

		@Override protected void onEnd(WorldObject binding) {};
		
	}
	
	/** The sequence node used for incrementing the save node. **/
	private final class SaveNodeIncrementer extends SequenceNode {
		
		private final SaveNode node;

		public SaveNodeIncrementer(Sequence sequence, SaveNode node) {
			super(sequence);
			this.node = node;
		}

		@Override public int branches() {
			return 1;
		}

		@Override public SequenceNode getBranch(int n) {
			return node;
		}

		@Override protected void onBegin(WorldObject binding) {
			node.setBackup(node.getBackup() + 1);
			seq.next(0);
		}

		@Override protected void onEnd(WorldObject binding) {};
		
	}

}
